import assert from 'assert';
import dayjs from 'dayjs';
import { Op, where } from 'sequelize';
import Decimal from 'decimal.js';
import config from 'config'; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
import superagent from 'superagent';
import {
  min, find, get, keyBy, remove, trim, map, first, toUpper
} from 'lodash';
import SMSCaptcha from '../models/sms-captcha';
import {
  generateOutTradeNo,
  toNumber,
  WxPrepayPaidStatus,
  getPrimaryResvNameId,
  getPrimaryConfirmNumber,
  getToday,
  getReservationIds,
  getHotelParameters,
  getHotel,
  formatLocalError
} from '../utils';
import { getSequelize } from '../db';
import HotelModel from '../models/hotel';
import RoomTypeModel from '../models/room-type';
import RoomModel from '../models/room';
import RoomFloorModel from '../models/room-floor';
import RoomFeatureModel from '../models/room-feature';
import AppParameterValueModel from '../models/app-parameter-values';
import DepositRuleModel from '../models/deposit-rule';
import OpenUserModel from '../models/open-user';
import ReservationPrepayModel from '../models/reservation-prepay';
import ReservationRefundModel from '../models/reservation-refund';
import MiniAccessLogModel from '../models/mini-access-log';
import MiniUrlModel from '../models/mini-url';
import RoomHistoryModel from '../models/room-history';
import { getWxPay } from '../utils/WxPay';
import {
  WxPayTransactionsJsapi as WxPayTransactionsJsapiModel,
  WxPayNotify as WxPayNotifyModel,
  WxPayRefundNotify as WxPayRefundNotifyModel
} from '../mongodb';

async function resolveRoomType(hotel, reservation) {
  const roomTypeCode = get(reservation, 'RoomStay.RoomType.roomTypeCode');
  let roomType;
  if (roomTypeCode) {
    roomType = await RoomTypeModel.findOne({
      where: { hotel_id: hotel.id, code: roomTypeCode },
      include: [{
        model: DepositRuleModel,
        as: 'deposit_rule'
      }]
    });

    if (roomType) {
      reservation.roomTypeDescription = roomType.name;
    }
  }

  return roomType;
}

async function resolveDeposit(reservation, deposit) {
  /*
  const RULE_TYPES = {
    FLAT: 'FLAT', // 一笔固定金额
    NIGHT_FLAT: 'NIGHT_FLAT', // 固定金额*Nights
    PECENT: 'PECENT', // 总费用*Percent
    FIRST_NIGHT_PECENT: 'FIRST_NIGHT_PECENT', // 首晚房价*Percent
    NIGHTS: 'NIGHTS', //前N晚房费作为押金
  }
  */

  // computedReservationStatus The possible values are RESERVED, PROSPECT, NOSHOW, CANCELED, INHOUSE, CHECKEDOUT, CHANGED, WAITLISTED and PRECHECKEDIN.

  const dailyRates = [];
  let depositAmount = 0;
  let dueToPay = 0;
  let totalAmount = 0;

  if (reservation.computedReservationStatus === 'DUEIN') { // DUE IN 才计算押金
    totalAmount = toNumber(get(reservation, 'RoomStay.Total.value', 0));
    const nights = toNumber(get(reservation, 'RoomStay.Nights', 0));
    const ChargesForPostingDate = get(reservation, 'RoomStay.ExpectedCharges.ChargesForPostingDate', []);
    for (const dailyRate of ChargesForPostingDate) {
      dailyRates.push({
        PostingDate: dailyRate.PostingDate,
        TotalCharges: toNumber(dailyRate.TotalCharges)
      });
    }

    if (deposit) {
      if (deposit.rule_type === 'FLAT') {
        depositAmount = deposit.amount;
      }
      else if (deposit.rule_type === 'NIGHT_FLAT') {
        depositAmount = Decimal.mul(deposit.amount, nights).toNumber();
      }
      else if (deposit.rule_type === 'PECENT') {
        depositAmount = Decimal.mul(deposit.amount / 100, totalAmount).toNumber();
      }
      else if (deposit.rule_type === 'FIRST_NIGHT_PECENT') {
        const firstNightAmount = get(dailyRates, '0.TotalCharges', 0);
        depositAmount = Decimal.mul(deposit.amount / 100, firstNightAmount).toNumber();
      }
      else if (deposit.rule_type === 'NIGHTS') {
        let amount = new Decimal(0);
        for (let i = 0; i < min(nights, deposit.amount); i++) {
          amount = amount.plus(get(dailyRates, `${i}.TotalCharges`, 0));
        }
        depositAmount = amount.toNumber();
      }
    }

    dueToPay = new Decimal(totalAmount).plus(depositAmount).toNumber();
  }

  reservation.Deposit = {
    dailyRates,
    totalAmount,
    depositAmount,
    dueToPay
  };
}

async function resolveShareDeposit(hotel, reservation, deposit, shareDepositRule) {
  // const shareDepositRule = await AppParameterValues.findOne({
  //   where: {
  //     hotel_id: hotel.id,
  //     app_parameter_name_id: 'SHARE_DEPOSIT_RULE'
  //   },
  //   raw: true
  // });

  const rule = get(shareDepositRule, 'app_parameter_value');
  const reservations = [reservation];
  if (rule === 'Integration') { // share合并支付
    if (Array.isArray(reservation.ShareReservationDetails) && reservation.ShareReservationDetails.length > 0) {
      // 获取 share 预订
      for (const share of reservation.ShareReservationDetails) {
        if (trim(share.confirmNumber) !== trim(reservation.confirmNumber)) {
          const response = await getReservation(hotel, share.confirmNumber);
          const resv = response.body.data;
          reservations.push(resv);
        }
      }
    }
  }

  remove(reservations, (item) => item.computedReservationStatus !== 'DUEIN');
  if (reservations.length === 0) {
    const error = new Error('未查询到符合押金规则的预订');
    error.code = 'NO_DEPOSIT_RESV';
    throw error;
  }

  let totalDueToPay = new Decimal(0);
  for (const item of reservations) {
    await resolveDeposit(item, deposit);
    totalDueToPay = totalDueToPay.plus(item.Deposit.dueToPay);
  }

  return { reservations, totalDueToPay: totalDueToPay.toNumber() };
}

async function getReservation(hotel, confirmNumber) {
  const service = config.get('localService');
  const response = await superagent.post(`${service}/remote-call`)
    .set('Content-Type', 'application/json')
    .accept('application/json')
    .send({
      hotelId: hotel.id,
      api: `reservation/confirmation-number/${confirmNumber}`,
      method: 'get'
    });

  if (response.status === 200) {
    if (get(response, 'body.errCode') === 0) {
      return response;
    }
    throw new Error(formatLocalError(response.body.errMsg));
  }
  else {
    throw new Error(response.text);
  }
}

// wxData 微信支付通知数据
async function postDeposit(hotel, reservationPrepay, wxData) {
  const service = config.get('localService');
  const response = await superagent.post(`${service}/remote-call`)
    .set('Content-Type', 'application/json')
    .accept('application/json')
    .send({
      hotelId: hotel.id,
      api: 'reservation/make-payment',
      method: 'post',
      out_trade_no: wxData.out_trade_no,
      wx_transaction_id: wxData.transaction_id,
      charge: wxData.amount.total / 100,
      resvNameId: reservationPrepay.primary_resv_name_id
    });

  if (response.status === 200) {
    if (get(response, 'body.errCode') === 0) {
      return response;
    }
    throw new Error(response.body.errMsg);
  }
  else {
    throw new Error(response.text);
  }
}

// 押金退款
async function postDepositRefund(hotel, reservationRefund, wxData) {
  const service = config.get('localService');
  const response = await superagent.post(`${service}/remote-call`)
    .set('Content-Type', 'application/json')
    .accept('application/json')
    .send({
      hotelId: hotel.id,
      api: 'reservation/make-payment',
      method: 'post',
      out_trade_no: wxData.out_refund_no,
      wx_transaction_id: wxData.refund_id,
      charge: -(wxData.amount.refund / 100),
      resvNameId: reservationRefund.primary_resv_name_id
    });

  if (response.status === 200) {
    if (get(response, 'body.errCode') === 0) {
      return response;
    }
    throw new Error(response.body.errMsg);
  }
  else {
    throw new Error(response.text);
  }
}

/*
  result 正确示例
  {
    status: 200,
    data: {
      appId: 'wx254b44aaf45b9c6e',
      timeStamp: '1718685694',
      nonceStr: 'zaa9lefgcqo',
      package: 'prepay_id=wx181241351036424dc2ce01fe20613c0000',
      signType: 'RSA',
      paySign: 'gn4n9KhHOu6TL92VPWQ7TfTbxf9HDLktlSmEuMr6zJ414qzb8FXeGTuypJokIQmeqNED5dWVo8PF3RaCPTeLrRUe/T+r3BHnRPk2A1NjXaEohT+Z9wj/xFmTCOOGKzcDsyw1wEE+BhCc8dkgB7ZgqD+uhgkbrHtBRrCGNq9wgvcLddv1S5nuWxIrmwu7ig2VlFIQTO2AX61d1Hw34JS16HX6lDKcQtr9zjIlUhWMZpw177chnlw3VqD8t2PzTRddDflgr21wWgzFyD2jiOEIqdO21ZtQYr065UWzxWy6Jk+mpvbGIVdmZuOHoDWUwMDr+Ytill6WKZ75iIod0FWpqQ=='
    }
  }

  result 错误示例
  {
    status: 400,
    errRaw: {
      status: 400,
      response: {
        req: {},
        header: {},
        status: 400,
        text: '{"code":"PARAM_ERROR","detail":{"location":"body","value":1500},"message":"无法将 JSON 输入源“/body/amount”映射到目标字段“订单金额”中，此字段需要一个合法的 Json 对象"}'
      }
    },
    error: '{"code":"PARAM_ERROR","detail":{"location":"body","value":1500},"message":"无法将 JSON 输入源“/body/amount”映射到目标字段“订单金额”中，此字段需要一个合法的 Json 对象"}'
  }
*/
async function getPrepayId(hotel, reservations, amount, openid) {
  const tradeNo = generateOutTradeNo();
  const keyParams = await getHotelParameters(hotel);
  if (!get(keyParams, 'WXPAY_NOTIFY_URL')) {
    throw new Error('未设置微信通知链接参数');
  }

  if (!get(keyParams, 'WXPAY_GOODS_DESCRIPTION')) {
    throw new Error('未设置微信支付商品描述参数');
  }

  const params = {
    description: get(keyParams, 'WXPAY_GOODS_DESCRIPTION'),
    out_trade_no: tradeNo,
    notify_url: get(keyParams, 'WXPAY_NOTIFY_URL'),
    amount: {
      total: amount * 100,
      currency: 'CNY'
    },
    payer: {
      openid
    },
    attach: `确认号: ${map(reservations, 'confirmNumber').join(',')}`
  };

  console.log('jsapi下单参数', params);
  const pay = getWxPay(hotel);

  const result = await pay.transactions_jsapi(params);
  if (result.status !== 200) {
    throw new Error(result.error);
  }

  result.data.prepay_id = result.data.package.replace('prepay_id=', '');
  console.log('jsapi下单响应', result);

  WxPayTransactionsJsapiModel.create({
    ...params,
    hotel_id: hotel.id, // 酒店id, 系统生成
    comfirm_number: map(reservations, 'confirmNumber'), // 确认号
    resv_name_id: map(reservations, 'resvNameId'),
    primary_confirm_number: getPrimaryConfirmNumber(reservations),
    primary_resv_name_id: getPrimaryResvNameId(reservations),
    appid: hotel.appid, // 由微信生成的应用ID，全局唯一。请求基础下单接口时请注意APPID的应用属性，例如公众号场景下，需使用应用属性为公众号的服务号APPID 示例值：wxd678efh567hg6787
    mchid: hotel.mchid, // 直连商户的商户号，由微信支付生成并下发。 示例值：1230000109
    prepay_id: result.data.prepay_id,
    wxpay: result.data
  });

  return { params, result };
}

// 下单或查询之前有效单据
async function placeOrder(hotel, reservations, openid, totalDueToPay, shareDepositRule, transaction) {
  let reservationPrepay = await ReservationPrepayModel.findOne({
    where: {
      hotel_id: hotel.id,
      primary_confirm_number: getPrimaryConfirmNumber(reservations),
      refund_amount: 0 // 有退款的话可以重新支付
    }
  });

  let _result;

  if (!reservationPrepay) {
    // jsapi下单
    const { params, result } = await getPrepayId(hotel, reservations, totalDueToPay, openid);
    const { out_trade_no } = params;

    const value = {
      hotel_id: hotel.id,
      openid,
      appid: hotel.appid,
      mchid: hotel.mchid,
      confirm_number: map(reservations, 'confirmNumber').join(','),
      resv_name_id: map(reservations, 'resvNameId').join(','),
      primary_confirm_number: getPrimaryConfirmNumber(reservations),
      primary_resv_name_id: getPrimaryResvNameId(reservations),
      out_trade_no,
      prepay_id: result.data.prepay_id,
      expires_at: dayjs().add(110, 'minutes').toDate(), // 2小时有效期，减10分钟作为预留时间
      amount: totalDueToPay,
      paid_status: WxPrepayPaidStatus.Waiting,
      paid_time: null,
      share_deposit_rule: shareDepositRule.app_parameter_value,
      is_refund: false
    };

    reservationPrepay = await ReservationPrepayModel.create(value, { transaction });
    _result = result;
  }
  else {
    if (reservationPrepay.paid_status === WxPrepayPaidStatus.Success) {
      const error = new Error('请勿重复支付');
      error.code = 'DUPLICATE_DEPOSIT';
      throw error;
    }

    // if (dayjs(reservationPrepay.expires_at).isBefore(dayjs())) { // prepay_id过期了，需要重新下单
    // 必须重新jsapi下单
    const { params, result } = await getPrepayId(hotel, reservations, totalDueToPay, openid);
    const { out_trade_no } = params;
    reservationPrepay.openid = openid;
    reservationPrepay.appid = hotel.appid;
    reservationPrepay.mchid = hotel.mchid;
    reservationPrepay.confirm_number = map(reservations, 'confirmNumber').join(',');
    reservationPrepay.resv_name_id = map(reservations, 'resvNameId').join(',');
    reservationPrepay.primary_confirm_number = getPrimaryConfirmNumber(reservations);
    reservationPrepay.primary_resv_name_id = getPrimaryResvNameId(reservations);
    reservationPrepay.out_trade_no = out_trade_no;
    reservationPrepay.prepay_id = result.data.prepay_id;
    reservationPrepay.expires_at = dayjs().add(110, 'minutes').toDate(); // 2小时有效期，减10分钟作为预留时间
    reservationPrepay.amount = totalDueToPay;
    reservationPrepay.paid_status = WxPrepayPaidStatus.Waiting;
    reservationPrepay.vpaid_time = null;
    reservationPrepay.share_deposit_rule = shareDepositRule.app_parameter_value;
    reservationPrepay.is_refund = false;
    await reservationPrepay.save({ transaction });
    _result = result;
    // } else {
    //   const data = {
    //     appId: reservationPrepay.appid,
    //     timeStamp: parseInt(+new Date() / 1000 + '').toString(),
    //     nonceStr: Math.random()
    //       .toString(36)
    //       .substr(2, 15),
    //     package: "prepay_id=".concat(reservationPrepay.prepay_id),
    //     signType: 'RSA',
    //     paySign: '',
    //   };
    //   const str = [data.appId, data.timeStamp, data.nonceStr, data.package, ''].join('\n');
    //   const pay = getWxPay(hotel);
    //   data.paySign = pay.sign(str);

    //   _result = {
    //     data
    //   }
    // }
  }

  return { reservationPrepay, result: _result };
}

// order docs https://sequelize.org/docs/v6/core-concepts/model-querying-basics/
class WeChatController {
  async redirect(req, res, next) {
    try {
      const { code } = req.params;
      const id = first(code.match(/\d+/g));
      const miniUrl = await MiniUrlModel.findOne({ where: { id }, raw: true, rejectOnEmpty: new Error('无效的链接') });
      console.log(miniUrl);
      res.redirect(miniUrl.url);
    }
    catch (e) {
      next(e);
    }
  }

  async jscode2session(req, res, next) {
    try {
      const { jsCode } = req.params;
      const hotel = await getHotel(req);
      const response = await superagent.get(`https://api.weixin.qq.com/sns/jscode2session?appid=${hotel.appid}&secret=${hotel.appsecret}&js_code=${jsCode}&grant_type=authorization_code`);
      const data = JSON.parse(response.text);

      if (!data.openid) {
        throw new Error(data.errmsg);
      }

      let openuser = await OpenUserModel.findOne({
        where: {
          appid: hotel.appid,
          openid: data.openid
        }
      });

      if (openuser) {
        Object.assign(openuser, data);
      }
      else {
        openuser = OpenUserModel.build({
          hotel_id: hotel.id,
          appid: hotel.appid,
          ...data
        });
      }
      await openuser.save();
      res.sendOk(data);
    }
    catch (e) {
      next(e);
    }
  }

  // https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_5.shtml
  /*
  {
    id: '6e82d012-0773-5ab2-b714-7aa389820080',
    create_time: '2024-06-18T14:15:12+08:00',
    resource_type: 'encrypt-resource',
    event_type: 'TRANSACTION.SUCCESS',
    summary: '支付成功',
    resource: {
      original_type: 'transaction',
      algorithm: 'AEAD_AES_256_GCM',
      ciphertext: 'N5Kgh+QzicmYjOOhcRRiMsk67/xh/comXYcmRuD8DT6jJGSJ0KXN5ISVP0TSXd8qISIBa4ylC7RAB6EF9AghtmdHi2sCseCUrf9ye9ehocKaFdZA1zc3T+6k22kv3USIHE4r79ohIVKrHQMlHU/5fd50wA+tQ2MjB0aZV1nMgib7tph1cGpaTST++ZHmP5GbvZ1e9r/JYgdUkCoAL9WuxZEP+5ISWVW+E0UEr9tQwG60QapHFepFgVnbQWqCflzk8jAMOQwQo27QPSi//sCmishBQjD9orUq9YcjQaJROkM8gDkpB+Zqo92Fw2S1ElxLjXWwslwnoFgX89Wn5M1myJwZuiLPBsQcDVsYak/ctKr/7kzaUW5zgSW4IUu8fQ8P3K6CrkUZ2sjlBpN4B0L1GrHEmlCOim/JDlGqybrNtxnOpeDHUZHuOtlU7WSmxUYutU2DsjXBXGzqaK4Gs1heVdUASX2oxHuLblft34BI3zlvB4PHpKiEVkktRU1V9hMSokxPUY4z7xTkcIRTN9kTV9FXHLqmRM0/aADWRyY3il8fMHiQibdM9iwPjG5wHGzKPAmnzVlBchPq54NqzMMefQ03+HBhd0Ng5txMeHC2xBS9eN8kOQplYg==',
      associated_data: 'transaction',
      nonce: 'XwZuSYg1Zy2p'
    }
  }
  */

  async wxpayNotify(req, res, next) {
    try {
      // req.hostname lkhg.vms.wuyi-dev.com
      const hotel = await getHotel({ headers: { hotelcode: req.hostname.split('.')[0] } });

      const data = req.body;
      data.hotel_id = get(hotel, 'id');

      const pay = getWxPay(hotel);
      const text = pay.decipher_gcm(data.resource.ciphertext, data.resource.associated_data, data.resource.nonce, get(hotel, 'apiv3key'));
      if (typeof text === 'object') {
        res.status(200).end(); // 先给微信返回ok
        Object.assign(data, text);

        await WxPayNotifyModel.updateOne({ id: data.id }, data, { upsert: true });
        const reservationPrepay = await ReservationPrepayModel.findOne({
          where: {
            out_trade_no: data.out_trade_no
          }
        });

        if (reservationPrepay && !reservationPrepay.is_pms_posted) {
          try {
            // opera 押金入账
            await postDeposit(hotel, reservationPrepay, data);
            reservationPrepay.is_pms_posted = true;
          }
          catch (e) {
            console.error('押金入账PMS失败', e);
            reservationPrepay.is_pms_posted = false;
          }

          reservationPrepay.wx_transaction_id = data.transaction_id;
          reservationPrepay.paid_status = WxPrepayPaidStatus.Success;
          reservationPrepay.paid_time = dayjs().toDate();
          // 更新账务记录
          await reservationPrepay.save();
        }
      }
      else {
        res.status(500).send('Bad Request');
      }
    }
    catch (e) {
      console.error(e);
      res.status(500).send('Bad Request');
    }
  }

  // https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_11.shtml
  /*
  {
      "id":"EV-2018022511223320873",
      "create_time":"2018-06-08T10:34:56+08:00",
      "resource_type":"encrypt-resource",
      "event_type":"REFUND.SUCCESS",
      "summary":"退款成功",
      "resource" : {
          "original_type": "refund",
          "algorithm":"AEAD_AES_256_GCM",
          "ciphertext": "...",
          "associated_data": "",
          "nonce": "..."
      }
  }
  */
  async wxpayRefundNotify(req, res, next) {
    try {
      // req.hostname lkhg.vms.wuyi-dev.com
      const hotel = await getHotel({ headers: { hotelcode: req.hostname.split('.')[0] } });

      const data = req.body;
      data.hotel_id = get(hotel, 'id');

      const pay = getWxPay(hotel);
      const text = pay.decipher_gcm(data.resource.ciphertext, data.resource.associated_data, data.resource.nonce, get(hotel, 'apiv3key'));

      if (typeof text === 'object') {
        Object.assign(data, text);
        res.status(200).end(); // 先给微信返回ok

        await WxPayRefundNotifyModel.updateOne({ id: data.id }, data, { upsert: true });
        const sequelize = getSequelize();
        const transaction = await sequelize.transaction();
        req.transaction = transaction;
        const reservationPrepay = await ReservationPrepayModel.findOne({ where: { out_trade_no: data.out_trade_no }, transaction });
        const reservationRefund = await ReservationRefundModel.findOne({ where: { out_refund_no: data.out_refund_no }, transaction });

        if (reservationPrepay && reservationRefund) {
          if (data.refund_status === 'SUCCESS') {
            try {
              reservationPrepay.is_refund = true;
              reservationPrepay.refund_amount += reservationRefund.refund_amount;
              if (reservationRefund.post_to_pms) {
                // opera 押金入账
                await postDepositRefund(hotel, reservationRefund, data);
                reservationRefund.is_pms_posted = true;
              }
            }
            catch (e) {
              console.error('押金入账PMS失败', e);
              reservationRefund.is_pms_posted = false;
            }
          }

          reservationRefund.refund_id = data.refund_id;
          reservationRefund.refund_status = data.refund_status;
          reservationRefund.success_time = data.success_time;
          // 更新账务记录
          await reservationPrepay.save({ transaction });
          await reservationRefund.save({ transaction });
          await transaction.commit();
        }
      }
      else {
        res.status(500).send('Bad Request');
      }
    }
    catch (e) {
      console.error(e);
      res.status(500).send('Bad Request');
    }
  }

  async getHotelInfo(req, res, next) {
    try {
      const hotel = await getHotel(req);
      const data = {
        hotel_name: hotel.display_name || hotel.name,
        images: [],
        logo: hotel.logo,
        my_image: hotel.my_image
      };

      if (hotel.image1) data.images.push(hotel.image1);
      if (hotel.image2) data.images.push(hotel.image2);
      if (hotel.image3) data.images.push(hotel.image3);
      if (hotel.image4) data.images.push(hotel.image4);

      data.parameters = await getHotelParameters(hotel);
      MiniAccessLogModel.create({
        hotel_id: hotel.id,
        openid: req.headers.openid || null,
        ip_address: req.clientIPv4
      });

      res.sendOk(data);
    }
    catch (e) {
      next(e);
    }
  }

  async getBillByOpenID(req, res, next) {
    try {
      const { openid } = req.params;
      const { start, end, page } = req.query;
      const limit = 10;

      if (!dayjs(start, 'YYYY-MM-DD').isValid() || !dayjs(end, 'YYYY-MM-DD').isValid()) {
        throw new Error('无效的查询日期');
      }

      if (!page >= 1) {
        throw new Error('无效的查询参数');
      }

      const hotel = await getHotel(req);

      const data = await ReservationPrepayModel.findAndCountAll({
        where: {
          hotel_id: hotel.id,
          openid,
          paid_time: {
            [Op.between]: [dayjs(start, 'YYYY-MM-DD').toISOString(), dayjs(end, 'YYYY-MM-DD').add(1, 'days').toISOString()]
          }
        },
        order: [['paid_time', 'DESC']],
        limit,
        offset: (page - 1) * limit,
        raw: true
      });

      for (const row of data.rows) {
        row.display_time = dayjs(row.paid_time).format('YYYY-MM-DD HH:mm');
      }

      res.sendOk(data);
    }
    catch (e) {
      next(e);
    }
  }

  async getRoomHistoryByOpenID(req, res, next) {
    try {
      const { openid } = req.params;
      const { start, end, page } = req.query;
      const limit = 10;

      if (!dayjs(start, 'YYYY-MM-DD').isValid() || !dayjs(end, 'YYYY-MM-DD').isValid()) {
        throw new Error('无效的查询日期');
      }

      if (!page >= 1) {
        throw new Error('无效的查询参数');
      }

      const hotel = await getHotel(req);

      const data = await RoomHistoryModel.findAndCountAll({
        where: {
          hotel_id: hotel.id,
          openid,
          business_date: {
            [Op.between]: [start, end]
          }
        },
        order: [['created_at', 'DESC']],
        limit,
        offset: (page - 1) * limit,
        raw: true
      });

      for (const row of data.rows) {
        row.display_time = dayjs(row.created_at).format('YYYY-MM-DD HH:mm');
      }

      res.sendOk(data);
    }
    catch (e) {
      next(e);
    }
  }

  /*
  data
  {
    access_token: '78_hrQc4_qfcppB9Mu_xbS3QNZEBh929a...ckHxHSBUD5xcTEs_wakvmlu3V7k',
    expires_in: 7200,
    refresh_token: '78_DYgpcU0EowVN8Ybu2yZz5MOJGV0n-...hSv9Pd4YdHq9WJ5auSXKe7vooCew',
    openid: 'oZeckxFX_Lp_1WIkXmbCyqJ6YNm4',
    scope: 'snsapi_base'
  }
  */

  async sms(req, res, next) {
    // console.log(req);
    const { phoneNumbers } = req.body;
    const { openid } = req.headers;

    const random = Math.random().toString();
    const code = random.substring(2, 8); // 验证码

    try {
      const hotel = await getHotel(req);

      const SendSmsResponse = await SMSClient.main({
        phoneNumbers,
        templateParam: JSON.stringify({ code })
      });
      if (get(SendSmsResponse, 'body.code') === 'OK') {
        const data = await SMSCaptcha.create({
          hotel_id: hotel.id,
          openid,
          mobile: phoneNumbers,
          captcha: code,
          expired_at: dayjs().add(10, 'minutes').valueOf(),
          business_date: dayjs().format('YYYY-MM-DD'),
          ip_address: req.clientIPv4
        });

        res.sendOk(data);
      }
      else {
        throw new Error(`${get(SendSmsResponse, 'body.message', '获取验证码失败')}`);
      }
    }
    catch (e) {
      next(e);
    }
  }

  async getReservationByConfirmNumber(req, res, next) {
    const { confirmNumber } = req.params;

    try {
      const hotel = await getHotel(req);
      if (toUpper(trim(confirmNumber)) === 'TEST58740') { // 微信审核用
        const reservation = {
          reservationStatus: 'RESERVED',
          marketSegment: 'R',
          sourceCode: '58740',
          redemReservationFlag: 'false',
          noPost: 'false',
          DoNotMoveRoom: 'false',
          commentsExists: 'false',
          tracesExists: 'false',
          specialsExists: 'false',
          roomPreferenceExists: 'false',
          shareExists: 'false',
          queueExists: 'false',
          alertExists: 'false',
          messageExists: 'false',
          checkOutTime: '00:00:00.0000000+08:00',
          computedReservationStatus: 'DUEIN',
          checkInTime: '00:00:00.0000000+08:00',
          returningGuest: 'false',
          confirmNumber: '7711281',
          resvNameId: '415452',
          legNumber: '1',
          RoomStay: {
            RatePlan: {
              ratePlanCode: 'RROD',
              suppressRate: 'false',
              RatePlanDescription: 'Standard Rate'
            },
            RoomType: {
              roomTypeCode: 'RM1',
              numberOfUnits: 1,
              roomToChargeCode: 'RM1',
              roomClass: 'ALL',
              RoomTypeDescription: '1 Queen Superior Room NSMK with free WiFi, mini bar, wet bar, coffee/tea maker, hair dryer',
              RoomNumber: ''
            },
            RoomRate: {
              roomTypeCode: 'RM1',
              ratePlanCode: 'RROD',
              Rate: {
                value: 0,
                currencyCode: 'CNY',
                decimals: '2'
              }
            },
            GuestCount: {
              ADULT: '1',
              CHILD: '0'
            },
            StartDate: '2025-10-29',
            EndDate: '2025-10-30',
            Nights: 1,
            Total: {
              value: 0,
              currencyCode: 'CNY',
              decimals: '2'
            }
          },
          ResGuestRPHs: [
            {
              RPH: '0'
            }
          ],
          ResGuests: [
            {
              Profile: {
                nameID: '943617',
                PersonName: {
                  nameTitle: '',
                  firstName: 'shi',
                  lastName: 'Ce',
                  middleName: '',
                  nameSuffix: ''
                },
                NativeName: {
                  languageCode: 'C',
                  nameTitle: '',
                  firstName: '',
                  lastName: '测试1',
                  middleName: '',
                  nameSuffix: ''
                },
                Phone: {}
              },
              resGuestRPH: '0'
            }
          ],
          lastName: 'Ce',
          firstName: 'shi',
          fullName: 'Ce, shi',
          altName: '测试1'
        };
        await resolveRoomType(hotel, reservation);

        res.send({
          errCode: 0,
          errMsg: 'request:ok',
          data: reservation
        });
      }
      else {
        const response = await getReservation(hotel, confirmNumber);

        const reservation = response.body.data;
        await resolveRoomType(hotel, reservation);

        res.send(response.body);
      }
    }
    catch (e) {
      next(e);
    }
  }

  async getShareReservationDepositByConfirmNumber(req, res, next) {
    const { confirmNumber } = req.params;
    const { openid } = req.headers;

    try {
      const hotel = await getHotel(req);
      const response = await getReservation(hotel, confirmNumber);

      const reservation = response.body.data;
      const roomType = await resolveRoomType(hotel, reservation);

      const shareDepositRule = await AppParameterValueModel.findOne({
        where: {
          hotel_id: hotel.id,
          app_parameter_name_id: 'SHARE_DEPOSIT_RULE'
        },
        rejectOnEmpty: new Error('未配置合住预订押金支付规则'),
        raw: true
      });

      const data = await resolveShareDeposit(hotel, reservation, get(roomType, 'deposit_rule'), shareDepositRule);

      if (!req.transaction) {
        req.transaction = await getSequelize().transaction();
      }

      console.log('-----------------++++-----------------');
      // console.dir(data, { depth: null })
      // wechatpay jspai 下单
      const { reservationPrepay, result } = await placeOrder(hotel, data.reservations, openid, data.totalDueToPay, shareDepositRule, req.transaction);

      await req.transaction.commit();
      res.sendOk({ ...data, prepay_id: reservationPrepay.prepay_id, wxpay: result.data });
    }
    catch (e) {
      next(e);
    }
  }

  async getRoomAvailability(req, res, next) {
    const { roomTypeCode } = req.query;
    const service = config.get('localService');

    try {
      assert(roomTypeCode, '无效的房型');
      const hotel = await getHotel(req);
      const roomType = await RoomTypeModel.findOne({ where: { hotel_id: hotel.id, code: roomTypeCode }, rejectOnEmpty: new Error('无效的房型') });
      const floors = await RoomFloorModel.findAll({ where: { hotel_id: hotel.id, disabled: false }, order: [['sequence', 'ASC'], ['code', 'ASC']], raw: true });
      const keyFloors = keyBy(floors, 'id');

      const rooms = await RoomModel.findAll({
        where: { hotel_id: hotel.id, room_type_id: roomType.id, selectable: true },
        include: [{
          model: RoomFloorModel,
          as: 'room_floor'
        }, {
          model: RoomFeatureModel,
          as: 'features',
          attributes: ['id', 'code', 'name', 'sequence']
        }],
        order: [['sequence', 'ASC'], ['code', 'ASC']]
      });

      const keyParams = await getHotelParameters(hotel);

      const response = await superagent.post(`${service}/remote-call`)
        .set('Content-Type', 'application/json')
        .accept('application/json')
        .send({
          hotelId: hotel.id,
          api: 'housekeeping/room-status',
          method: 'post',
          roomTypeCode,
          roomStatus: get(keyParams, 'SELECT_ROOM_ROOM_STATUS'),
          hkStatus: get(keyParams, 'SELECT_ROOM_HK_STATUS'),
          foStatus: get(keyParams, 'SELECT_ROOM_FO_STATUS'),
          reservationStatus: get(keyParams, 'SELECT_ROOM_RESV_STATUS')
        });

      if (response.status === 200) {
        if (response.body.errCode === 0) {
          const pmsRooms = response.body.data;
          remove(rooms, (item) => !find(pmsRooms, { RoomNumber: item.code }));
        }
        else {
          throw new Error(formatLocalError(response.body.errMsg));
        }
      }
      else {
        throw new Error(response.text);
      }

      for (const room of rooms) {
        keyFloors[room.room_floor_id].match = true;
        if (!keyFloors[room.room_floor_id].rooms) {
          keyFloors[room.room_floor_id].rooms = [];
        }

        keyFloors[room.room_floor_id].rooms.push(room);
      }

      remove(floors, (e) => !e.match);

      res.sendOk(floors);
    }
    catch (e) {
      next(e);
    }
  }

  async assignRoom(req, res, next) {
    const { openid } = req.headers;
    const { resvNameId, room, reservation } = req.body;
    const service = config.get('localService');

    try {
      const hotel = await getHotel(req);

      const response = await superagent.post(`${service}/remote-call`)
        .set('Content-Type', 'application/json')
        .accept('application/json')
        .send({
          hotelId: hotel.id,
          api: 'reservation/assign-room',
          method: 'post',
          resvNameId,
          room
        });

      if (response.status === 200) {
        if (response.body.errCode === 0) {
          res.sendOk(response.body.data);
          // 选房日志
          RoomHistoryModel.create({
            hotel_id: hotel.id,
            openid,
            ...getReservationIds(reservation),
            origin_room: get(reservation, 'RoomStay.RoomType.RoomNumber', null),
            room,
            business_date: getToday()
          });
        }
        else {
          throw new Error(response.body.errMsg);
        }
      }
      else {
        throw new Error(response.text);
      }
    }
    catch (e) {
      next(e);
    }
  }
}

export default new WeChatController();
